#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/mm.h>        
#include <asm/io.h>
#include <linux/fb.h>
#include <linux/slab.h>         

#define DEVICE_NAME "fison_mmap_drv"
#define MIN(x,y) (x < y ? x : y)

static char* kernel_buf;
static int buf_size = 1024*8;

static int major;
static struct class *mmap_class;


static ssize_t mmap_read(struct file *fd, char __user *buf, 
    size_t size, loff_t *lof){
    int err ;
    
    err = copy_to_user(buf, kernel_buf, MIN(1024, size));
        if(err){
        pr_err("Failed to copy data to user space (error: %d)\n", err);
        return -EFAULT;
    }

    return MIN(1024, size);
}

static ssize_t mmap_write(struct file *fd, const char __user *buf, 
    size_t size, loff_t *lof){

    return 0; 
}

static int fison_mmap(struct file *file, struct vm_area_struct *vma){
    unsigned long phy;
    int err;
   
    vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

    phy = virt_to_phys(kernel_buf);
    err = remap_pfn_range(vma, vma->vm_start, phy >> PAGE_SHIFT, 
            vma->vm_end - vma->vm_start, vma->vm_page_prot);
    if(err){
        printk("mmap remap_pfn_range error");
        return -ENOBUFS;
    }

    return 0;
}

static int mmap_open(struct inode *node, struct file *fd){
    
    return 0;
}

static int mmap_close(struct inode *node, struct file *fd){
    return 0;
}

static struct file_operations mmap_ops = {
    .owner   = THIS_MODULE,
	.read    = mmap_read,
	.write   = mmap_write,
    .open    = mmap_open,
    .release = mmap_close,
    .mmap    = fison_mmap,
};

static int __init fison_mmap_init(void) {
    kernel_buf = (char *)kmalloc(buf_size, GFP_KERNEL);
    if (!kernel_buf) {
        return -ENOMEM;
    }

    int ret = 0;
    major = register_chrdev(0, DEVICE_NAME, &mmap_ops);
    if (major < 0) {
        ret = major;
        printk(KERN_ERR "[%s]: Failed to register char device, error %d\n",__func__, ret);
        goto err_register_chrdev;
    }

    mmap_class = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(mmap_class)) {
        ret = PTR_ERR(mmap_class);
        printk(KERN_ERR "[%s]: Failed to create class, error %d\n", __func__, ret);
        goto err_class_create;
    }
    
    if (!device_create(mmap_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME)) {
        ret = -ENODEV;
        printk(KERN_ERR "[%s]: Failed to create device\n",__func__);
        goto err_device_create;
    }

    printk(KERN_INFO "[%s]: sucessed to create device\n", __func__);
    return 0;

    err_device_create:
        class_destroy(mmap_class);

    err_class_create:
        unregister_chrdev(major, DEVICE_NAME);

    err_register_chrdev:
        return ret;
}

static void __exit fison_mmap_exit(void){
    kfree(kernel_buf);
    device_destroy(mmap_class, MKDEV(major, 0));
    class_destroy(mmap_class);
    unregister_chrdev(major, DEVICE_NAME);

    printk(KERN_INFO "device %s(%d) unregisted\n", DEVICE_NAME, major);
}

module_init(fison_mmap_init);
module_exit(fison_mmap_exit);

MODULE_AUTHOR("fison");
MODULE_DESCRIPTION("mmap");
MODULE_LICENSE("GPL");